Note: This tutorial assumes you have installed ros-electric-brown-remotelab and ros-electric-brown-drivers. |
Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags. |
Using Javascript to control ROS via rosjs
Description: This tutorial will show you how to write HTML web-pages that interact with ROS.Keywords: Javascript
Tutorial Level: BEGINNER
Contents
Introduction
ROS is a large and sophisticated research tool used and developed by hundreds (if not thousands) of roboticist world-wide and rosjs exposes almost all of its capabilities out-of-the-box. Obviously, it will take more than a few days to master the skills necessary to take over the world via a robot army, but it's surprisingly easy to get a robot running around driven by logic running right in your browser. In fact, that's what this tutorial is all about. We assume at least a passing familiarity with Javascript and it's idioms (such as callbacks) but no ROS or robot specific knowledge. If you have access to an iRobot Create and a web-browser, you should be able to follow along.
A Viciously Short Primer on ROS
Background
Before you can make use of rosjs, there are a few things you need to know about ROS itself. Besides the practical stuff like installation, running, etc. (which we'll get to), it's important to know how ROS is architected and how it exposes all of the capabilities it provides.
If you're familiar with ROS, feel free to skim or skimp this section. Most of the material will be covered in more depth in later sections. On the flipside: if you're very unfamiliar with ROS, you would do well checking out the great tutorials and other documentation on ROS.org.
Topics and Services
ROS exposes capabilities in one of two ways, as topics or as services. services should be very familiar to you if you've programmed before. They are very similar to Javascript's function call. services take arguments and return a response. One important difference from more vanilla functions you may have used in the past is that services always respond with (return) an object. As in most programming languages, this object may have fields and in this way a service may return almost arbitrarily complicated data.
topics are streams of objects more akin to Javascript events. You register a handler function (much as you would a listener in Javascript) and whenever a new topic object is available, the handler is called with that object as it's argument. Completing the analogy with Javascript events: just as you can "generate" Javascript events, you can "publish" topic objects which will then be processed by all of the handlers that have subscribed to them.
This is probably all getting a bit abstract, so let's look at a concrete example of using both a topic and a service.
connection.addHandler('/sensorPacket',function(msg) { if (msg.bumpLeft || msg.bumpRight) { alert('bump'); } }); connection.callService('/rosjs/subscribe','["/sensorPacket",0]',function(rsp) { alert('subscribed to /sensorPacket'); });
Don't worry if you find much of this code confusing, the point here is to get more familiar with the concepts of topics and services. We will cover everything in more detail as we go along. This above snippet has examples of both. Consider:
connection.addHandler('/sensorPacket',function(msg) { if (msg.bumpLeft || msg.bumpRight) { alert('bump'); } });
We'll try not to belabor the parallels to Javascript's addEventListener. Here a handler function is associated with the '/sensorPacket' topic. Notice that /sensorPacket is proceeded by a slash. Now whenever the rosjs environment receives an object from the /sensorPacket topic it will be passed (as an argument named msg) to the anonymous function created here. How does the rosjs environment tell ROS that it's interested in /sensorPacket objects? Through a service call:
connection.callService('/rosjs/subscribe','["/sensorPacket",0]',function(rsp) { alert('got response: ' + rsp); });
The service we are calling is named /rosjs/subscribe (as above, the slashes are important). We give /rosjs/subscribe its arguments in the form of a JSON object. If you are unfamiliar with JSON, you can read more about it. Here, we are manually creating a JSON object, but we could just as easily have used a JSON library to help us. The arguments are the name of the topic that we want to subscribe to: /sensorPacket and the minimum delay (in milliseconds) between topic objects that we can tolerate (in this case we don't really care). This argument can never be used to make topic objects stream in faster than they would have, but it can be used to slow them down.
You might think that service responses would be in the form of a return value from the ros.callService function. However, to allow for maximum flexibility (and in keeping with the idioms of Javascript), service responses are handled by a callback as shown. This callback is mandatory and must be a valid (callable) function. Thus it's a common rosjs idiom to define a nop function
function nop() {};
and to use it with service calls you don't particularly care about the response to
connection.callService('/soundOf','["tree falling in woods"],nop);
Notice that the /soundOf service expects only one argument, while /rosjs/subscribe expects two. Unlike ROS service calls, rosjs calls are somewhat asynchronous. You still cannot make more than one call to the same service before receiving a response (doing so will have ill-defined behavior), but callService calls _will_ return immediately. Remember that this limitation only apply to calls to the same service, you can make calls to different services in as rapid a fashion as you'd like.
Besides being able to subscribe to a stream of objects associated with a particular topic, rosjs can also _create_ them. This is referred to as "publishing" to a topic.
//code that assigns the proper value to x and y ... connection.publish('/mouse','mouse_msgs/CursorPosition','{"x":' + x + ',"y":' + y + '}');
The publish function takes three arguments: 1) the topic to publish to 2) the type of the object to publish (again, we'll cover this more in-depth later) 3) the JSON form of the object to publish. Notice that once again, for the sake of making these examples self-contained we have manually created a JSON object. In real code, it would be far easier and less error prone to depend on a JSON library. Note that while the service name is proceeded by a slash, the type is not. You can try it the other way, but it won't work.
Nodes and Types
At its heart, ROS is really a Remote Procedure Call (RPC) and data-sharing mechanism. ROS provides a server program, roscore, that acts a central registry for (amongst other things) topics and services. This facilitates ROS-compatible programs, called nodes, communicating with each other through named channels (topics and services). Of course, just having a /topic or /service is not enough to facilitate communication. There must me some kind of hope that the programs will understand one another. This is where types come in. In ROS, an object's structure (the organization of its fields, and the kinds of values those fields can take on) is called its _type_. ROS nodes can be confident in their ability to process the objects published by a particular topic because each topic is associated with a specific type. In our earlier example, we published to the /mouse topic. In this case the /mouse topic is associated with the mouse_msgs/CursorPosition type. If a node knew how to examine mouse_msgs/CursorPosition objects, it could confidently subscribe to /mouse knowing there would be no surprises in the structure of the data it receives.
While it would be next to impossible to make use of ROS without some knowledge of the types involved, with rosjs the only time you need to explicitly handle ROS types is when publishing.
Dive into rosjs
Enough talk about abstractions and types and whatnot. Let's move a robot from our web-browser! To get started there are a few things you'll need:
Prerequisites
rosjs is not necessarily specific to the materials listed here, but we do use them as our example.
1) the brown_remotelab stack 2) the brown_drivers stack 3) An iRobot Create
- We've written this tutorial with this robot in mind, but with slight adaptation, but the rosjs examples themselves should work with any that respond to Twist msgs on /cmd_vel.
4) A websocket compatible browser, such as the latest Chrome, Safari, or Firefox (others may work just fine too).
You can see if your browser supports websockets here.
Setting up your rosjs environment
Start up a roscore, if you haven't already.
roscore &
Attach the create robot to /dev/ttyUSB0 and start up the driver
rosrun irobot_create_2_1 driver.py &
If your robot is attached to a port other than /dev/ttyUSB0, you'll need to set a rosparam _before_ using rosrun
rosparam set /brown/irobot_create_2_1/port /dev/otherPort
Finally, you're ready to run rosbridge which will allow rosjs to connect
rosrun rosbridge rosbridge.py
If you don't get an error from rosbridge, you're ready to write your first rosjs enabled webpage.
About rosjs webpages
rosbridge listens for websocket connections on port 9090 of the machine it's started on. Since rosjs with rosbridge via websockets and not AJAX, the machine that serves the HTML content does not necessarily need to be the same machine that rosbridge is running on. With rosjs, a direct connection is made from the web-browser viewing the page and the machine running rosbridge. If the browsing machine can reach 9090 on the machine running rosbridge, all is well. If it can't... well... not much will happen. Make routing arrangements accordingly.
One advantage of this flexibility is that you don't need a web-server to start experimenting with rosjs, you can view a locally hosted file and everything will work just as it would if the page were served from Apache, IIS, or what-have-you.
Your First rosjs webpage
We'll start with the following HTML skeleton
<html> <head> <script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/tags/brown-ros-pkg/rosbridge/ros.js"></script> <script type="text/javascript"> function main() { /* insert code here */ } </script> </head> <body onload="main()"> </body> </html>
The first script tag imports the latest minified version of rosjs. The second is where we'll define the main function called by the body onload.
The Create's basic motion is controlled by two parameters a forward velocity, x, and a angular velocity z. We'll start by defining two variables for these parameters.
var x = 0; var z = 0;
Now to create a rosjs connection object. This object will be our gateway to ROS services and topics. To initialize it, we'll need the address or hostname of the machine running rosjs.
var connection = new ros.Connection("ws://hostname:9090");
To use this rosjs connection object, we need to register three callbacks: one for when a server connection becomes closed, one for when errors occur, and one for when a connection is successfully opened.
connection.setOnClose(function (e) { document.write('connection closed<br/>'); }); connection.setOnError(function (e) { document.write('error!<br/>'); }); connection.setOnOpen(function (e) { document.write('connected to ROS<br/>'); });
Here's what our webpage looks like so far:
<html> <head> <script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/tags/brown-ros-pkg/rosbridge/ros.js"></script> <script type="text/javascript"> </script> </head> <body onload="main()"> function main() { var x = 0; var z = 0; var connection = new ros.Connection("ws://hostname:9090"); connection.setOnClose(function (e) { document.write('connection closed<br/>'); }); connection.setOnError(function (e) { document.write('error!<br/>'); }); connection.setOnOpen(function (e) { document.write('connected to ROS<br/>'); }); </body> </html>
Since we need a connection to ROS to do pretty much anything at all, most of the action will happen inside the OnOpen callback.
First let's define a local function that will set the Create's linear and angular velocity based on the values of x and z.
function pub() { connection.publish('/cmd_vel', 'geometry_msgs/Twist', '{"linear":{"x":' + x + ',"y":0,"z":0}, "angular":{"x":0,"y":0,"z":' + z + '}}'); }
As with our earlier examples, we are hand-crafting a JSON object. This is fine for this toy example, but in general you will want to make use of a real JSON library.
So now by calling pub, we can set the robot's motion to that of x and z. Not very interesting if x and z remain 0. Here's where Javascript's and the browser's extensive even handling come into play. We'll define a function that provides some basic key controls.
function handleKey(code, down) { var scale = 0; if (down == true) { scale = 1; } switch (code) { case 37: //left z = 1 * scale; break; case 38: //up x = .5 * scale; break; case 39: //right z = -1 * scale; break; case 40: //down x = -.5 * scale; break; } pub(); }
Now we simply register this function with the as a key event handler for the webpage body.
document.addEventListener('keydown', function (e) { handleKey(e.keyCode, true); }, true); document.addEventListener('keyup', function (e) { handleKey(e.keyCode, false); }, true);
The complete webpage looks like this:
<html> <head> <script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/tags/brown-ros-pkg/rosbridge/ros.js"></script> <script type="text/javascript"> function main() { var x = 0; var z = 0; var connection = new ros.Connection("ws://hostname:9090"); connection.setOnClose(function (e) { document.write('connection closed<br/>'); }); connection.setOnError(function (e) { document.write('error!<br/>'); }); connection.setOnOpen(function (e) { document.write('connected to ROS<br/>'); function pub() { connection.publish('/cmd_vel', 'geometry_msgs/Twist', '{"linear":{"x":' + x + ',"y":0,"z":0}, "angular":{"x":0,"y":0,"z":' + z + '}}'); } function handleKey(code, down) { var scale = 0; if (down == true) { scale = 1; } switch (code) { case 37: //left z = 1 * scale; break; case 38: //up x = .5 * scale; break; case 39: //right z = -1 * scale; break; case 40: //down x = -.5 * scale; break; } pub(); } document.addEventListener('keydown', function (e) { handleKey(e.keyCode, true); }, true); document.addEventListener('keyup', function (e) { handleKey(e.keyCode, false); }, true); }); } </script> </head> <body onload="main()"> </body> </html>
That's really it. Change hostname to the name or IP of the machine running rosjs, and you're good to go. Load the page in a websocket supporting browser (you can check for compatibility here) and drive the robot with the arrow keys. Remote teleop in less than 60 lines of HTML!
Teleop is great and all, but what about actual control? Here is a more realistic example that implements wall following.
<html> <head> <script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/tags/brown-ros-pkg/rosbridge/ros.js"></script> <script type="text/javascript"> function nop() {} /* console for logging */ var console = null; /* state */ var active = false; var wall = false; var bump = false; function log(msg) { console.innerHTML = console.innerHTML + msg + "<br/>"; } function init() { function waitForDOM() { var cnsl = document.getElementById('console'); if (cnsl == null) { setTimeout(waitForDOM, 100); } else { console = cnsl; setTimeout(main, 0); } } setTimeout(waitForDOM, 100); } function main() { log('console initialized'); var connectInfo = document.location.toString(); log('url: ' + connectInfo); var addressMatches = connectInfo.match(/address=([^&]*)/); if (addressMatches == null) { log('Problem extracting address!'); return; } var address = addressMatches[1]; log('address: ' + address); var portMatches = connectInfo.match(/.*&port=([^&]*)/); if (portMatches == null) { log('Problem extracting port!'); return; } var port = portMatches[1]; log('port: ' + port); log('creating ROSProxy connection object...'); var connection = null; try { connection = new ros.Connection('ws://' + address + ':' + port); } catch (err) { log('Problem creating proxy connection object!'); return; } log('created'); log('connecting to ' + address + ' on port ' + port + '...'); connection.setOnClose(function (e) { log('connection closed'); }); connection.setOnError(function (e) { log('network error!'); }); connection.setOnOpen(function (e) { log('connected'); log('initializing ROSProxy...'); try { connection.callService('/rosjs/topics', '[]', nop); } catch (error) { log('Problem initializing ROSProxy!'); return; } log('initialized'); log('registering handler for sensorPacket...'); try { connection.addHandler('/sensorPacket', function (msg) { bump = false; if (msg.bumpLeft || msg.bumpRight) { bump = true; } wall = msg.wall; if (msg.advance) { active = false; } if (msg.play) { active = true; } }); } catch (error) { log('Problem registering handler!'); return; } log('registered'); log('subscribing to sensorPacket...'); try { connection.callService('/rosjs/subscribe', '["/sensorPacket",0]', nop); } catch (error) { log('Problem subscribing!'); } log('subscribed'); log('setting closed loop control policy...'); var aligned = true; var turned = 0; var target = 150; function twistMsg(x, z) { return '{"linear":{"x":' + x + ',"y":0,"z":0},"angular":{"x":0,"y":0,"z":' + z + '}}'; } setInterval(function () { var x = 0; var z = 0; if (wall) aligned = true; if (bump) aligned = false; if (!aligned) { if (active) { z = .5; turned = turned + 1; } } else { x = .25; turned = 0; target = 150 + Math.floor(Math.random() * 150); if (!wall) z = -.5; } if (turned > target) aligned = true; if (!active) { x = 0; z = 0; } connection.publish('/cmd_vel', 'geometry_msgs/Twist', twistMsg(x, z)); }, 100); log('running'); }); } </script> </head> <body onload="init()"> <div id="console"></div> </body> </html>
To use this controller, place it in an html file as before. Once you've loaded it in your browser you'll need to give the script the host and port that it should connect to. You do this from the URL itself. For example, if your browser displays this address:
file:///Users/aka1/Desktop/test.html
You would tell it to connect to 9090 on hostname by extending the URL like so:
file:///Users/aka1/Desktop/test.html?address=maria&port=9090
Once the page is reporting that the controller is running, you can start it by pushing the play button on the Create. You can pause it with the advance button. Closed loop control from a web-browser!
Advanced rosjs webpages
For the sake of clarity, our examples have done a number of things you wouldn't want to do when doing "real" work. To preserve your sanity, we highly recommend that you use a Javascript framework. rosjs should be compatible with your favorite. Being from Brown, we like Flapjax.
Also almost essential is a JSON library. rosjs takes service call arguments and objects to publish as JSON objects, and a library will help make generating these much simpler.
Programming ROS from rosjs
Including the rosjs Library
To make use of rosjs, you must somehow include the rosjs library in your Javascript. As shown in our previous examples, one of the easiest ways to do this is through a script tag that sources the latest version on-line.
<script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/tags/brown-ros-pkg/rosbridge/ros.js"></script>
Creating a rosjs Connection Object
To use rosjs, you instantiate a rosjs connection object to connect to rosbridge. The constructor takes a single argument, a websocket URL.
var connection = new ros.Connection('ws://hostname:port');
We now have a rosjs connection object referenced by a variable connection. To make real use of rosjs, you must also register handlers for the closing, error, and connection opening events. This is done through three functions: setOnClose, setOnError, and setOnOpen. The functions take a single argument: a handler function (taking in a single argument).
connection.setOnClose(function (e) { //pass }); connection.setOnError(function (e) { //pass }); connection.setOnOpen(function(e) { //pass });
The setOnOpen handler is where the action is, and can be thought of as the entry point for rosjs programs.
Service Calls
rosjs provides access to underlying ROS services and topics. It also provides some unique services of its own. These services are meant to help you manage a ROS environment from within rosjs and provide many of the same services that the ROS command line tools do. These services are accessed exactly like "real" ROS services, so we will cover making service calls first.
A rosjs service call takes the following form:
connection.callService('/service', JSONArg, function(resp) { //handle response });
where '/service' is a string containing the service name, JSONArg is a JSON list of arguments, and the handler function is a way of handling any response the service returns. All three arguments are required, even the handler. If you don't take about the response, you will still need to supply a handler that does nothing.
Another important point is that despite accessing ROS services through callbacks, you are still limited to making *one* call to a particular service at a time. Only the last registered callback is remembered, so if you make a second service call to a particular service _before_ the first response has been received both responses will be handled by the last callback. Lastly, remember that the JSONArg list is always a list. If you are passing no arguments, you should pass an empty list. If you are passing in a single argument, you should still wrap it in a list.
rosjs Services
rosjs provides a number of services itself, they are accessed exactly as you would a ROS service. They are accessible _only_ from within rosjs (they are not exported for use by other ROS nodes).
/rosjs/topics
- This service returns a list of the currently available topics. It takes an empty argument list.
/rosjs/services
- This service returns a list of the currently available services. It takes an empty argument list.
/rosjs/subscribe
- Use this service to subscribe to topics that you've already registered a handler for ( see the Topic Handlers section)
/rosjs/log This service is intended to provide longer term JSON storage. It takes two arguments, an alphanumeric identifier and a string. It creates a (server) local file containing the submitted string named for the identifier.
/rosjs/authorize rosjs has a key authorization capability. See the Security section.
/rosjs/typeStringFromTopic
- Provided at least one message has already been published to a topic, this service will return the type associated with that topic. The service takes a list with a single entry: the topic string of interest.
/rosjs/typeStringFromService
- This service is the equivalent of /rosjs/typeStringFromTopic, but for services.
/rosjs/classFromTopic
- Takes a list with a single string element denoting the topic of interest. It returns a JSON object representing the structure of the type associated with that topic.
/rosjs/classesFromService
- The equivalent of /rosjs/classFromTopic, but for services.
/rosjs/msgClassFromTypeString
- Takes a list whose single element is a type-string. Returns a JSON version of the appropriate message class.
/rosjs/reqClassFromTypeString, /rosjs/rspClassFromTypeString
- Services types have two classes associated with them, a request object and a return object. These two services will take a list with a type-string entry and return the appropriate JSON equivalent.
Topic Handlers
Topics are handled in rosjs via the registration of handler callbacks. First you register a callback with the local rosjs connection object. You then use a service call to announce to the rosjs server your interest in that topic.
var bump = false; connection.addHandler('/sensorPacket',function(msg) { if (msg.bumpLeft || msg.bumpRight) { bump = true; } else { bump = false; } }); connection.callService('/rosjs/subscribe','["/sensorPacket",0]',function(e) {});
Notice that since we aren't particularly interested in the result of the call to /rosjs/subscribe, we pass an empty handler. When a new topic object is received it will be passed as an argument to the topic handler function. For details about the format of this object see the Types and JSON section.
/rosjs/subscribe arguments
Besides the topic name, you may have noticed the 0 argument in the above example. This is the minimum amount of time the rosjs server should wait between streaming objects of this type. If you wanted to receive a maximum of ten /sensorPacket objects per second, you could have used this call instead.
connection.callService('/rosjs/subscribe','["/sensorPacket",100]',function(rsp) {});
This instructs the server to wait 100 milliseconds between objects.
The pause factor argument is mandatory, but there is another---optional---argument set you can use when dealing with topics that contain images. By default, rosjs detects standard ROS images inside objects and changes them into data URI's. If you would like to control the the way in which this occurs, you should make the relevant subscription call appear as follows.
connection.callService('/rosjs/subscribe',json(['/imagetopic',0,'jpeg',128,96,100]),function(rsp) {});
This call request that the images be changed into 128x96 jpeg images at 100 percent quality. You can request images of any size and quality (keep bandwidth in mind though). However, rosjs only supports two encodings: png and jpeg.
Publishing
Publishing with rosjs is fairly straight-forward.
connection.publish('/topic','type-string',JSONobj);
The tricky bit is knowing the appropriate type-string and making sure that the JSON encoded object you pass as the last argument matches the expected structure of this type. Passing any other kind of structure will result in undefined behavior. For more details, see the next section. Again, remember that while topic and service names are proceeded by a slash, types are not. Types may well _contain_ slashes, but they do not begin with them.
Types and JSON
When ROS objects are passed to topic handlers, they are transformed into JSON objects with identical structure. Objects containing images are an important exception. Rather than pass the image data verbatim, fields that would contain an image are changed into sub-objects that contain a uri field. This field contains a Javascript compatible data-uri version of the image. In general you should not use image based topics with rosjs, you can get much better performance by visualizing data directly in the browser via Canvas or WebGL.
Keep in mind that this image->data-uri transformation only applies to reception. If for some reason you want to pass an image object up to the server, you will need to follow the same binary encoding rules as does python.
You can discover the type-strings and structures of services and topics you're interested in via the supplied rosjs services or via the command line utilities included with ROS. For the most part, you can ignore types, with the important exception of publishing.
rosjs uses a type-discovery mechanism that requires that at least one topic object has been published to a topic before rosjs can subscribe to it. For this reason, if you use rosjs with a node such as launch (at the brown ros pkg), you may need to introduce delays into your program so that there is time for at least one topic to be published. This restriction does not apply to publishing (which is why you have to provide a type-string when publishing).
Advanced Usage
Local Logging
Probably the easiest way to log data locally is to store it in a list, and then use a new window to display/save it when needed. Assuming that json() is our JSON stringifier:
function log() { var win = window.open('','log','menubar=1'); var txt = json(log); win.document.open(); win.document.write(txt); win.document.close(); return false; }
The return false is so that this function can be used comfortably from within an href. Depending on your browser, the following will allow you to _save source_ or at the very least copy and paste the data elsewhere.
Security
rosjs provides two forms of security: protected services/topics and key authorization. The protection mechanism is very straight-forward to use. If a topic or service begins with the string "protected" rosjs will block access as transparently as possible. Specifically, attempts to subscribe or publish to protected topics will seem to succeed, but no actual subscription or publication will take place. One notable way in which protected services are not transparent is in the structure of the response. All calls to protected services will result in an empty object being returned.
The key authorization mechanism is more involved. At the command line, rosjs can be given a keyurl parameter
./rosjs.py --keyurl http://somekeyserver.com/somecgi.pl
rosjs expects this URL to be a JSONP service that will take a key-value via a "key=" get argument and will return a JSON object with a duration field indicating the number of seconds (from now) this key is authorized to use the rosjs service. Unauthorized keys simply return a duration of 0 seconds. Keys are strings consisting of alphabetic and numerical characters.
Clients authorize themselves by using the /rosjs/authorize service, which takes a key-value as its only argument. Clients can authorize as many times as they wish. The duration from their latest key/attempt will be the one that applies to them. When rosjs is using authorization, a client must make its first call a call to the /rosjs/authorize service, all other calls will result in disconnection. When a clients current key has no more authorization time, they will be disconnected.
rosjs does not care how the key service comes to the duration conclusions it does (or how clients get their keys). You are free to use whatever external authorization mechanisms you want.
On machines where appropriate SSL libraries are available, rosjs should automatically support SSL (https) keyurl's.
rosjs does not take any steps to throttle connection attempts nor does it provide any other form of DOS (denial of service) protection. Users are encouraged to handle this issue at the port or firewall level.
ROS Launch Files
rosjs does not itself provide the ability to use ROS launch files. However, so a capability is easily available from the launch package provided by brown-ros-pkg:
svn co https://brown-ros-pkg.googlecode.com/svn/trunk/experimental/launcher launcher
Simply put the launch files you'd like rosjs to be able to access inside the bin directory of this node. The node provides a service that accepts a filename to launch.
Switching Topics
ROS already provides the ability to multiplex topics through the mux node.
Writing Plugins
If you build functionality on top of rosjs, we encourage you to modularize it and make it available to the community as a rosjs plugin. Doing so is easier than you might think. As you might have noticed throughout the tutorial, the rosjs connection functionality is exposed from within a global object named "ros". To produce a rosjs plugin, simply make your functionality available as an appropriate extension of this "namespace". Here's a trivial example representing a hypothetical "Hello World" plugin produced by the Hello Institute:
ros.helloco.helloworld = function() { alert('Hello, world!'); }
If this script were to be made available at http://helloco.com/rosjs/hello.js , then using this plugin is simply a matter of including the appropriate script tag _after_ including the initial rosjs library but _before_ any scripts using it.
<script type="text/javascript" src="http://brown-ros-pkg.googlecode.com/svn/tags/brown-ros-pkg/rosbridge/ros.js"></script> <script type="text/javascript" src="http://helloco.com/rosjs/hello.js"></script> <script type="text/javascript"> function main() { ros.helloco.helloworld(); //Hello, world! } </script>
Notice that the Hello Institute was kind enough to put their functionality into a unique sub-namespace based on their organizational domain. Not only does this keep the global ros namespace clean and avoid naming conflicts, it helps make clear the origin of functionality.
If your plugin will require keeping some kind of state, you should make use of a constructor or factory function. Here's a stateful example (also from the Hello Institute):
var StatelyHelloWorld = function() { this.number = 0; } StatelyHelloWorld.prototype.helloworld = function() { alert('Hello, ' + this.number + '.'); this.number++; } ros.helloco.StatelyHelloWorld = StatelyHelloWorld;
Users of this plugin are now free to use the stateful behavior easily:
var statelyHelloWorld = new ros.helloco.StatelyHelloWorld(); statelyHelloWorld.helloworld(); //Hello, 0. statelyHelloWorld.helloworld(); //Hello, 1. statelyHelloWorld.helloworld(); //Hello, 2. //and so on
Next Steps
rosjs is in a very early stage of development. We hope that you find the idea of using Javascript and other web-technologies as exciting as we do and help issue bug reports, documentation corrections, and suggestions to help us make it as great as possible.